﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Istace.Framework.Mapping.Core.Configuration;
using Istace.Framework.Mapping.Core.Interfaces;

namespace Istace.Framework.Mapping.Core.Internal
{
    /// <summary>
    /// Helper class for mapping framework initialization
    /// </summary>
    internal sealed class MappingFrameworkBootstrapper : IMappingFrameworkBootstrapper
    {
        #region Singleton instance
        private static IMappingFrameworkBootstrapper _instance;
        /// <summary>
        /// GetInstance the default instance for the bootstraper
        /// </summary>
        public static IMappingFrameworkBootstrapper Instance
        {
            get
            {
                return _instance ?? (_instance = new MappingFrameworkBootstrapper());
            }
        }
        #endregion 

        #region Private properties
        /// <summary>
        /// List of the assemblies which are already browsed by the bootstraper
        /// </summary>
        private readonly List<AssemblyName> AlreadyBrowsedAssemblies;
        #endregion

        #region Private .ctor
        /// <summary>
        /// Default .ctor
        /// </summary>
        private MappingFrameworkBootstrapper()
        {
            this.AlreadyBrowsedAssemblies = new List<AssemblyName>();
        }
        #endregion

        #region Public methods
        /// <summary>
        /// Initialize the mappinf framework for the current executing assembly.
        /// </summary>
        /// <remarks>
        /// For instance, we mention My.Assembly in the configuration as the mapper container assemly.
        /// If inspectReference is set to true, all the assemblies referenced by My.Assemly will be inspected to check if they also contain mapper class.
        /// </remarks>
        public void Initialize()
        {
            this.Initialize(true, false);
        }

        /// <summary>
        /// Initialize the mappinf framework for the current executing assembly.
        /// </summary>
        /// <param name="inspectReference">If true, all referenced assemblies will be inspected</param>
        /// <param name="reBrowse">If true, event if the assembly is already browsed, we will rebrowse it</param>
        /// <remarks>
        /// For instance, we mention My.Assembly in the configuration as the mapper container assemly.
        /// If inspectReference is set to true, all the assemblies referenced by My.Assemly will be inspected to check if they also contain mapper class.
        /// </remarks>
        public void Initialize(bool inspectReference, bool reBrowse)
        {
            Initialize(Assembly.GetExecutingAssembly(), inspectReference, reBrowse);
        }

        /// <summary>
        /// Initialize the mapping framework
        /// </summary>
        /// <param name="baseAssembly">The base assembly</param>
        /// <param name="inspectReference">If true, all referenced assemblies will be inspected</param>
        /// <param name="reBrowse">If true, even if the assembly is already browsed, we will rebrowse it</param>
        public void Initialize(Assembly baseAssembly, bool inspectReference, bool reBrowse)
        {
            //Load the ShiConfiguration section from the .Config file.
            MappingAssemblyConfigSection ConfigSettings = MappingAssemblyConfigSection.GetConfigSection();

            // If user want to re browse the assambly, clear the AlreadyBrowsedAssemblies cache
            if (reBrowse)
                this.AlreadyBrowsedAssemblies.Clear();

            //Only populate if the section exists.
            if (ConfigSettings != null)
            {
                // For each assembly configured in the app.config, browse the assembly and register each
                // type which implement the IMapper interface and have the TYpeMapper attribute defined.
                for (int i = 0; i < ConfigSettings.RegisteredAssemblies.Count; i++)
                {
                    BrowseAssembly(Assembly.Load(ConfigSettings.RegisteredAssemblies[i].Assembly), inspectReference);
                }
            }
        }
        #endregion

        #region Private methods

        /// <summary>
        /// Inspect assemblies to find IMapper implementation with TypeMapperAttribute attribute defined
        /// </summary>
        /// <param name="assembly">The assembly to browse</param>
        /// <param name="inspectReference">If true, all referenced assemblies will be inspected</param>
        private void BrowseAssembly(Assembly assembly, bool inspectReference)
        {
            // Check if the assemly has already been browsed,
            AssemblyName assemblyName = assembly.GetName();
            if (!AlreadyBrowsedAssemblies.Exists((an) => an.FullName == assemblyName.FullName))
            {
                AlreadyBrowsedAssemblies.Add(assemblyName);

                // Iterate over each typep contained by the assembly. If the type implement the IMapper interface and have
                // the TypeMapperAttribute attribute defined, register the mapper to the MapperService.
                foreach (Type type in assembly.GetTypes())
                {
                    if (type.GetInterfaces().ToList().Exists((t) => t.GetInterfaces().Contains(typeof(IMapper))))
                    {
                        foreach (MappingTuple tuple in this.GetTupleFromMapperType(type))
                        {
                            if (tuple != null)
                            {
                                MapperService.Instance.TryRegister(Activator.CreateInstance(type) as IMapper, tuple);
                            }
                        }
                    }
                }

                // If the user ask to browse referenced assemblies, iterate over each assemblies which are not yet browsed
                // by the bootstraper.
                if (inspectReference)
                {
                    foreach (AssemblyName assemblyRef in assembly.GetReferencedAssemblies())
                    {
                        if (!AlreadyBrowsedAssemblies.Exists((an) => an.FullName == assemblyRef.FullName))
                        {
                            BrowseAssembly(Assembly.Load(assemblyRef), true);
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Gets the tuple from interface.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <returns></returns>
        /// <exception cref="System.ArgumentNullException">type</exception>
        public IEnumerable<MappingTuple> GetTupleFromMapperType(Type type)
        {
            if(type == null) throw new ArgumentNullException("type");

            foreach(var declaredInterface in type.GetInterfaces().ToList())
            {
                if (declaredInterface.IsGenericType)
                {
                    List<Type> genericArguments = declaredInterface.GetGenericArguments().ToList();
                    if (genericArguments.Count == 2)
                    {
                        yield return new MappingTuple(genericArguments[0], genericArguments[1]);
                    }
                }
            }
        }
        #endregion
    }
}
